package com.toolbox.alui.taglib;

import com.plumtree.openfoundation.util.*;
import com.plumtree.openlog.OpenLogService;
import com.plumtree.openlog.OpenLogger;
import com.plumtree.portaluiinfrastructure.statichelpers.PTDebugHelpers;
import com.plumtree.portaluiinfrastructure.tags.*;
import com.plumtree.portaluiinfrastructure.tags.metadata.*;
import com.plumtree.server.*;
import com.plumtree.uiinfrastructure.constants.*;
import com.plumtree.xpshared.htmlelements.*;

/**
 * This tag displays EOD properties for the requested object, as well as
 * caching the data so that it can be reused throughout the request.
 */
public class EODTag extends ATag
{
	private static OpenLogger log = OpenLogService.GetLogger(
			OpenLogService.GetComponent(PTDebugHelpers.COMPONENT_PORTAL_UI_INFRASTRUCTURE),
			"com.toolbox.alui.taglib.EODTag");
	
	public static final ITagMetaData TAG;
	public static final OptionalTagAttribute CLASSID;
	public static final OptionalTagAttribute OBJECTID;
	public static final OptionalTagAttribute CURRENTUSER;
	public static final OptionalTagAttribute PROPNAME;
	public static final OptionalTagAttribute PROPID;

	// The prefix used to cache EOD properties in memory.
	public static final String STORAGE_PREFIX = "EOD";

	// The separator used to cache EOD properties in memory.
	public static final String STORAGE_SEPARATOR = ",";

	static
	{
		// The TAG member variable needs to be initialized first since it
		// can be used in other tags (i.e. as a RequiredParentTag) that
		// are referenced by member variables of this tag (i.e. as a
		// RelatedChildTag).  This is necessary for tags that have
		// circular references such as a parent / child tag relationship.
		TAG = new TagMetaData("eodprop",
			"This tag displays EOD properties for the current user.");

		CLASSID = new OptionalTagAttribute("classid",
			"This attribute specifies the class ID of the object whose EOD properties will be retrieved.  " +
			"The class ID is required unless the current user is being accessed.",
			AttributeType.INT, "-1");

		OBJECTID = new OptionalTagAttribute("objectid",
			"This attribute specifies the object ID of the object whose EOD properties will be retrieved.  " +
			"The object ID is required unless the current user is being accessed.",
			AttributeType.INT, "-1");

		CURRENTUSER = new OptionalTagAttribute("currentuser",
			"This attribute causes the EOD properties to be retrieved from the current user." +
			"If this attribute is set to true the class ID and object ID will be ignored.",
			AttributeType.BOOLEAN, "false");

		PROPID = new OptionalTagAttribute("propid",
			"This attribute specifies the ID of the EOD property to be retrieved.  " +
			"The property ID is required unless the property name is being used.  " +
			"If an ID is present, the property name attribute will be ignored.",
			AttributeType.INT, "-1");

		PROPNAME = new OptionalTagAttribute("propname",
			"This attribute specifies the name of the EOD property to be retrieved.  " +
			"The property name is required unless the property ID is being used.",
			AttributeType.STRING, "");
	}

	/**
	 * @see com.plumtree.portaluiinfrastructure.tags.ATag#DisplayTag()
	 */
	public HTMLElement DisplayTag()
	{
		int nClassID;
		int nObjectID;

		// Determine the proper object and class IDs to use.
		if (GetTagAttributeAsBoolean(CURRENTUSER))
		{
			IPTSession currentUser = (IPTSession) GetEnvironment().GetUserSession();

			nClassID = PT_CLASSIDS.PT_USER_ID;
			nObjectID = currentUser.GetUser().GetObjectID();
		} else
		{
			nClassID = GetTagAttributeAsInt(CLASSID);
			nObjectID = GetTagAttributeAsInt(OBJECTID);

			if ((nClassID == -1) || (nObjectID == -1))
			{
				throw new XPException("Missing class ID or object ID when not accessing current user.");
			}
		}

		// Query for the EOD properties for this object.
		IPTQueryResult qr = GetCachedEODProperties(nClassID, nObjectID);

		// Lookup which EOD property to access.
		int nPropID = GetTagAttributeAsInt(PROPID);
		String strPropertyName = GetTagAttributeAsString(PROPNAME);

		// Look through the query result for the requested EOD property
		String strEODValue = LookupProperty(nPropID, strPropertyName, qr);

		if (null == strEODValue)
		{
			throw new XPException("Unable to find property by ID (" + nPropID + ") or by name (" +
				strPropertyName + ") for class ID " + nClassID + " and object ID " + nObjectID + ".");
		}

		HTMLElementCollection result = new HTMLElementCollection();

		result.AddInnerHTMLString(strEODValue);

		return result;
	}

	/**
	 * This method gets the cached EOD property data for a given object.
	 * If the data is not already present in the cache, another method is
	 * called to query for the data and then the data is cached.
	 * 
	 * @param _nClassID The class ID of the object to query.
	 * @param _nObjectID The object ID of the object to query.
	 * @return The EOD property data.
	 */
	protected IPTQueryResult GetCachedEODProperties(int _nClassID, int _nObjectID)
	{
		// Calculate the cache key for this object
		String strCacheKey;
		strCacheKey = STORAGE_PREFIX + _nClassID + STORAGE_SEPARATOR + _nObjectID;

		// Get the cached EOD properties off of the request scope cache.
		// This cache will be cleared after every HTTP Request, so we don't
		// need to worry about cache management.  If you want to increase cache
		// performance, the data can be cached on the session or application
		// scope.  However, that could possibly require periodic invalidation of
		// the cache data so it doesn't get out of date, as well as being aware
		// of multi-threading issues if the data is cached on the application
		// (and available to multiple users at the same time). 
		Object oCachedValue = GetStateVariable(strCacheKey, Scope.HTTP_REQUEST);

		IPTQueryResult qr;

		// Check if we already have a cached query result.
		if (null != oCachedValue)
		{
			qr = (IPTQueryResult) oCachedValue;
		} else
		{
			// Query for the data since we do not have a cached query result.
			qr = GetEODProperties(_nClassID, _nObjectID);

			// Cache the query result so that it can be reused by other tags in this request.
			// NOTE: This will only cache the query result for the lifetime of the HTTP
			// request, after which it will be automatically cleaned up.  If an application
			// will be using a lot of EOD data in tags, and that data is not expected to change
			// much between requests, it will be faster to cache the query result on the session
			// or application.
			SetStateVariable(strCacheKey, qr, Scope.HTTP_REQUEST);
		}

		return qr;
	}

	/**
	 * This method queries for the EOD property data for a given object.
	 * 
	 * @param _nClassID The class ID of the object to query.
	 * @param _nObjectID The object ID of the object to query.
	 * @return The EOD property data.
	 */
	protected IPTQueryResult GetEODProperties(int _nClassID, int _nObjectID)
	{
		IPTQueryResult qr;

		// Get the object properties.
		IPTSession currentUser = (IPTSession) GetEnvironment().GetUserSession();
		IPTObjectManager objMgr = currentUser.GetObjectManagers(_nClassID);
		IPTObject curObject = (IPTObject) objMgr.Open(_nObjectID, false);
		IPTObjectProperties pProps = curObject.GetObjectProperties();

		// Query for the data.
		qr = pProps.GetPropertyData(PT_PROPIDS.PT_PROPID_OBJECTID |
									PT_PROPIDS.PT_PROPID_NAME | 
									PT_PROPIDS.PT_PROPID_PROP_VALUE);

		return qr;
	}

	/**
	 * This method looks up the requested property in the query result.
	 * 
	 * @param _nPropID The ID of the requested property (should be -1 if using name).
	 * @param _strPropertyName The name of the requested property (ignored if using ID).
	 * @param _qr The EOD property data.
	 * @return The value of the requestsed property.
	 */
	protected String LookupProperty(int _nPropID, String _strPropertyName, IPTQueryResult _qr)
	{
		String strEODValue = null;

		// Look through the query result for the requested EOD property
		for (int i = 0; i < _qr.RowCount(); i++)
		{
			if (_nPropID != -1)
			{
				// Lookup the property by ID
				int nCurrentPropID = _qr.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID);

				if (_nPropID == nCurrentPropID)
				{
					// Get the actual property value
					strEODValue = _qr.ItemAsString(i, PT_PROPIDS.PT_PROPID_PROP_VALUE);

					break;
				}
			} else
			{
				// Lookup the property by name
				String strCurrentName = _qr.ItemAsString(i, PT_PROPIDS.PT_PROPID_NAME);

				if (0 == XPStringUtility.CompareIgnoreCase(_strPropertyName, strCurrentName))
				{
					// Get the actual property value
					strEODValue = _qr.ItemAsString(i, PT_PROPIDS.PT_PROPID_PROP_VALUE);

					break;
				}
			}
		}

		return strEODValue;
	}

	/**
	 * @see com.plumtree.portaluiinfrastructure.tags.ATag#GetTagType()
	 */
	public TagType GetTagType()
	{
		// This tag does not display any of the HTML inside the tag,
		// and must be used as a singleton tag.
		return TagType.NO_BODY;
	}

	/**
	 * @see com.plumtree.portaluiinfrastructure.tags.ATag#SupportsAccessStyle(AccessStyles)
	 */
	public boolean SupportsAccessStyle(AccessStyles _style)
	{
		// This tag does not display any JavaScript, and therefore
		// supports all access styles.
		return true;
	}
	/**
	 * @see com.plumtree.portaluiinfrastructure.tags.ATag#Create()
	 */
	public ATag Create()
	{
		return new EODTag();
	}
}